//    Travel US - For users of travel.usairways.com
//    intended for US Airways employees and their dependents
//    This app will help you track your flights and display
//    Availability, flight status and other non-rev info
//    Copyright (C) 2014  Renats Stozkovs
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.bc004346.travelus.dataAccess;

import com.bc004346.travelus.model.TravelUsException;
import com.bc004346.travelus.model.TravelUsUnavailableException;
import com.bc004346.travelus.model.Tuple;
import com.bc004346.travelus.model.domain.Flight;
import com.bc004346.travelus.model.domain.FlightSearchParams;
import com.bc004346.travelus.model.domain.Passenger;
import com.bc004346.travelus.model.domain.Reservation;
import com.bc004346.travelus.model.domain.StringList;

import org.apache.commons.lang3.StringEscapeUtils;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import java.io.IOException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import static com.bc004346.travelus.utility.Utility.DATE;
import static com.bc004346.travelus.utility.Utility.DAY;
import static com.bc004346.travelus.utility.Utility.LONG_DATE;
import static com.bc004346.travelus.utility.Utility.SECOND;
import static com.bc004346.travelus.utility.Utility.SHORT_DATE;
import static com.bc004346.travelus.utility.Utility.SHORT_TIME;
import static com.bc004346.travelus.utility.Utility.log;

/**
 * Created by Renats on 1/20/14.
 * This is a data access screen scraper against
 * Travel US website
 */
public class TravelUsScreenScraper {

    //<editor-fold desc="Members">

    public static final String ICE_SESSIONS1 = "ice.sessions";
    private static final String ICE_SESSIONS = ICE_SESSIONS1;
    public static final String FIRST_PANE = "itineraryForm:j_id78FirstPane";
    public static final String SECOND_PANE = "itineraryForm:j_id78SecondPane";
    public static final String PERCENT = "itineraryForm:j_id78InPercent";
    public static final String SEARCH_RESULT = "itineraryForm:searchResult:0:_idcl";
    public static final String HEIGHT_100_WIDTH_20 = "height:100%;width:20%;";
    public static final String HEIGHT_100_WIDTH_77 = "height:100%;width:77%;";
    public static final String TRUE = "true";
    private static final String PARENTHESIS_START = "(";
    private static final String PARENTHESIS_END = ")";
    private static final String JSESSIONID = "JSESSIONID";
    private static final String END = "end...";
    private static final String TR = "tr";
    private static final String NSC = "NSC_wt_Qspe_QDJ_FUD_443";
    //<editor-fold desc="Book Flight section">
    private static final String BOOK_FLIGHT_NUMBER_0 = "td[id$=:j_id106:0:j_id107-0-0]";
    private static final String BOOK_FLIGHT_NUMBER_1 = "td[id$=:j_id106:1:j_id107-0-0]";
    private static final String BOOK_FLIGHT_NUMBER_2 = "td[id$=:j_id106:2:j_id107-0-0]";
    private static final String BOOK_DEPARTURE_0 = "td[id$=:j_id116:0:j_id117-0-0]";
    private static final String BOOK_DEPARTURE_1 = "td[id$=:j_id116:1:j_id117-0-0]";
    private static final String BOOK_DEPARTURE_2 = "td[id$=:j_id116:2:j_id117-0-0]";
    private static final String BOOK_ARRIVAL_0 = "td[id$=:j_id123:0:j_id124-0-0]";
    private static final String BOOK_ARRIVAL_1 = "td[id$=:j_id123:1:j_id124-0-0]";
    private static final String BOOK_ARRIVAL_2 = "td[id$=:j_id123:2:j_id124-0-0]";
    private static final String BOOK_ARRIVAL_ALT_0 = "td[id$=:j_id123:0:j_id127-0-0]";
    private static final String BOOK_ARRIVAL_ALT_1 = "td[id$=:j_id123:1:j_id127-0-0]";
    private static final String BOOK_ARRIVAL_ALT_2 = "td[id$=:j_id123:2:j_id127-0-0]";
    private static final String BOOK_FIRST_CLASS_0 = "td[id$=:j_id169:0:j_id174-0-0]";
    private static final String BOOK_FIRST_CLASS_1 = "td[id$=:j_id169:1:j_id174-0-0]";
    private static final String BOOK_FIRST_CLASS_2 = "td[id$=:j_id169:2:j_id174-0-0]";
    private static final String BOOK_COACH_0 = "td[id$=0:j_id199-0-0]";
    private static final String BOOK_COACH_1 = "td[id$=1:j_id199-0-0]";
    private static final String BOOK_COACH_2 = "td[id$=2:j_id199-0-0]";
    private static final String BOOK_FLIGHT_SEARCH_ERROR = "[address$=Error";
    //</editor-fold>
    //<editor-fold desc="Check PBT section">
    private static final String PBT_NON_REVS = "span[id$=:j_id78]";
    private static final String PBT_DESTINATION = "span[id$=:j_id54]";
    private static final String PBT_FLIGHT_NUMBER = "span[id$=:j_id46]";
    private static final String PBT_DEPARTURE_POINT = "span[id$=j_id50]";
    private static final String PBT_DEPARTURE_DATE = "span[id$=:j_id58]";
    private static final String PBT_FIRST_CAPACITY = "span[id$=:j_id62]";
    private static final String PBT_COACH_CAPACITY = "span[id$=:j_id82]";
    private static final String PBT_COACH_NON_REVS = "span[id$=:j_id98]";
    private static final String PBT_FIRST_SEATS_AVAILABLE = "a[id$=:j_id74]";
    private static final String PBT_COACH_SEATS_AVAILABLE = "a[id$=:j_id94]";
    private static final String PBT_REMOVE = "input[id$=:j_id102]";
    //</editor-fold>
    //<editor-fold desc="Profile section">
    private static final String PROFILE_NAME = "profileForm:j_id142";
    private static final String PROFILE_BADGE = "profileForm:j_id145";
    private static final String PROFILE_PNR = "a[id$=:listPnr]";
    private static final String PROFILE_TRAVEL_DATE = "span[id$=:j_id195]";
    private static final String PROFILE_BOOKING_TYPE = "span[id$=:j_id198]";
    private static final String PROFILE_SENIORITY = "profileForm:j_id148";
    private static final String PROFILE_IS_PNR_ACTIVE = "td[style=width:45px;overflow:hidden;;text-align:center;]";
    private static final String PROFILE_DETAILS_PASSENGERS = "pnrDetailsForm:j_id117";
    private static final String PROFILE_DETAILS_ITINERARY = "pnrDetailsForm:itins";
    private static final String PROFILE_TABLE = "table[class=iceDatTblScrlBdyTbl]";
    //</editor-fold>
    private static final String CHECKIN_SUCCESS = "checkinConfirmation.jsf";
    private static final String CHECKIN_TABLE = "table[class=iceDatTblScrlBdyTbl]";

    //<editor-fold desc="Check PBT section">
    private static final String CHECK_PBT_DATA_TABLE_ROW = "tr[class^=icePnlGrdRow]";
    private static final String CHECK_PBT_FORM_AVAILABILITY1 = "checkPbtForm:j_id69:0:j_id83";
    private static final String CHECK_PBT_FIRST = "First";
    private static final String CHECK_PBT_TOTAL_CAPACITY = "Total Capacity";
    private static final String CHECK_PBT_AUTHORIZED = "Authorized";
    private static final String CHECK_PBT_BOOKED = "Booked";
    private static final String CHECK_PBT_SPACE_AVAILABLE = "Space Available";
    private static final String CHECK_PBT_NON_REVS_LISTED = "Non Revs Listed";
    //</editor-fold>
    //<editor-fold desc="URI's section">
    private static final String URI_PROFILE = "https://travel.usairways.com/us/search/profile.jsf";
    private static final String URI_CHECK_IN = "https://travel.usairways.com/us/checkin/checkin.jsf";
    private static final String URI_CHECK_PBT = "https://travel.usairways.com/us/search/checkPbt.jsf";
    private static final String URI_ITINERARY = "https://travel.usairways.com/us/search/itinerary.jsf";
    private static final String URI_PBT_TRACKER = "https://travel.usairways.com/us/search/pbtTracker.jsf";
    private static final String URI_PNR_DETAILS = "https://travel.usairways.com/us/search/pnrDetails.jsf";
    private static final String URI_SECURITY_CHECK = "https://travel.usairways.com/us/search/j_security_check";
    private static final String URI_SELECT_PASSENGERS = "https://travel.usairways.com/us/search/passengers.jsf";
    private static final String URI_BOOKING_CONFIRMATION = "https://travel.usairways.com/us/search/confirmation.jsf";
    private static final String URI_SEND_RECEIVE_UPDATES = "https://travel.usairways.com/us/block/send-receive-updates";
    private static final String URI_CHECKIN_CONFIRMATION = "https://travel.usairways.com/us/checkin/checkin.jsf?boardPoint=%s&recLoc=%s&badgeNo=%s&companyCode=US";
    //</editor-fold>
    private static final String DATA_TABLE_ROW = "tr[class^=iceDatTblRow]";
    private static final String SPAN = "span";
    private static final String IFRAME = "iframe";
    private static final String UPDATE = "UPDATE";
    private static final String CONTENT = "content";
    private static final String STARTING = "starting...";
    private static final String USER_AGENT = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.3; WOW64; Trident/7.0; LCJB)";
    private static final String LOGIN_PAGE_ERROR_MESSAGE = "Please enter your user name and password that you use for Wings";
    private static final String TARGET = "ice.event.target";
    private static final String CAPTURED = "ice.event.captured";
    private static final String PROFILE_FORM = "profileForm";
    private static final String ICE_VIEW = "ice.view";
    private static final String ICE_FOCUS = "ice.focus";
    private static final String RAND = "rand";
    private static final String ICE_SESSION = "ice.session";
    private static final String JAVAX_FACES = "javax.faces.RenderKitId";
    private static final String PROFILE_FORM_PHONE_NUM = "profileForm:phoneNum";
    private static final String PROFILE_FORM_EMAIL_ADDRESS = "profileForm:emailAddress";
    private static final String PROFILE_FORM_TO = "profileForm:to";
    private static final String PROFILE_FORM_ELIGIBLE_PASSWORD_CONFIRM = "profileForm:eligiblePasswordConfirm";
    private static final String PROFILE_FORM_ELIGIBLE_PASSWORD = "profileForm:eligiblePassword";
    private static final String PROFILE_FORM_ELIGIBLE_ID_HIDDEN = "profileForm:eligibleIdHidden";
    private static final String PROFILE_FORM_ELIGIBLE_LAST_NAME_HIDDEN = "profileForm:eligibleLastNameHidden";
    private static final String PROFILE_FORM_ELIGIBLE_FIRST_NAME_HIDDEN = "profileForm:eligibleFirstNameHidden";
    private static final String PROFILE_FORM_LOGIN_ACTION_HIDDEN = "profileForm:loginActionHidden";
    private static final String RECLOC = "recloc";
    private static final String PROFILE_FORM_IDCL = "profileForm:_idcl";
    private static final String PROFILE_FORM_J_ID208_EXPANDED = "profileForm:j_id208Expanded";
    private static final String PROFILE_FORM_J_ID183_EXPANDED = "profileForm:j_id183Expanded";
    private static final String EMPTY = "";
    private String sessionId = EMPTY;
    private String profileSession = EMPTY;
    private String nsc = EMPTY;
    private String name = EMPTY;
    private String mUsername = EMPTY;
    private String mPassword = EMPTY;
    private static final String PROFILE_FORM_J_ID135_EXPANDED = "profileForm:j_id135Expanded";
    private static final String PROFILE_FORM_REC_LOOKUP = "profileForm:recLookup";
    private static final String ONE = "1";
    private static final String LIST_PNR = "profileForm:listings:0:listPnr";
    private static final String ADD_ONE = "#1";
    private static final String IC_EFACES_RENDER_KIT = "ICEfacesRenderKit";
    private static final String CHECK_PBT_FORM_SEARCH = "checkPbtForm:search";
    private static final String CHECK_PBT_FORM = "checkPbtForm";
    private static final String GET_PBT_DETAIL = "Get PBT Detail";
    private static final String CHECK_PBT_FORM_FLIGHT_NUMBER = "checkPbtForm:flightNumber";
    private static final String CHECK_PBT_FORM_BOARD = "checkPbtForm:board";
    private static final String CHECK_PBT_FORM_BOARD_IDX = "checkPbtForm:board_idx";
    private static final String CHECK_PBT_FORM_DEST = "checkPbtForm:dest";
    private static final String CHECK_PBT_FORM_DEST_IDX = "checkPbtForm:dest_idx";
    private static final String CHECK_PBT_FORM_DEPTDATE = "checkPbtForm:deptdate";
    private static final String CHECK_PBT_FORM_IDCL = "checkPbtForm:_idcl";
    private static final String CHECK_PBT_FORM_IDCHECK_PBT_FORM_DEPTDATESP = "checkPbtForm:_idcheckPbtForm:deptdatesp";
    private static final String CHECK_PBT_FORM_J_ID69_0_J_ID103 = "checkPbtForm:j_id69:0:j_id103";
    private static final String ITINERARY_FORM_SEARCH = "itineraryForm:search";
    private static final String SEARCH = "Search";
    private static final String ITINERARY_FORM = "itineraryForm";
    private static final String SA = "SA";
    private static final String ONE_WAY = "OneWay";
    private static final String ROUND_TRIP = "RoundTrip";
    private static final String ITINERARY_FORM_BOOKING_TYPE = "itineraryForm:bookingType";
    private static final String ITINERARY_FORM_TRIP_TYPE = "itineraryForm:tripType";
    private static final String ITINERARY_FORM_BOARD = "itineraryForm:board";
    private static final String ITINERARY_FORM_BOARD_IDX = "itineraryForm:board_idx";
    private static final String ITINERARY_FORM_DEST = "itineraryForm:dest";
    private static final String ITINERARY_FORM_DEST_IDX = "itineraryForm:dest_idx";
    private static final String ITINERARY_FORM_DD = "itineraryForm:dd";
    private static final String ITINERARY_FORM_J_ID50 = "itineraryForm:j_id50";
    private static final String ANY = "ANY";
    private static final String ITINERARY_FORM_RD = "itineraryForm:rd";
    private static final String ITINERARY_FORM_RTOD = "itineraryForm:rtod";
    private static final String ITINERARY_FORM_IDCL = "itineraryForm:_idcl";
    private static final String ICE_SUBMIT_PARTIAL = "ice.submit.partial";
    private static final String ICE_EVENT_TYPE = "ice.event.type";
    private static final String ONCLICK = "onclick";
    private static final String FALSE = "false";
    private static final String UNDEFINED = "undefined";
    private static final String X = "163";
    private static final String Y = "275";
    private static final String ICE_EVENT_ALT = "ice.event.alt";
    private static final String ICE_EVENT_CTRL = "ice.event.ctrl";
    private static final String ICE_EVENT_SHIFT = "ice.event.shift";
    private static final String ICE_EVENT_META = "ice.event.meta";
    private static final String ICE_EVENT_X = "ice.event.x";
    private static final String ICE_EVENT_Y = "ice.event.y";
    private static final String ICE_EVENT_LEFT = "ice.event.left";
    private static final String ICE_EVENT_RIGHT = "ice.event.right";
    private static final String ICEFACES_CSS_UPDATES = "icefacesCssUpdates";
    private static final String JAVAX_FACES_VIEW_STATE = "javax.faces.ViewState";
    private final int mTimeout = 30000;
    private String badge = "0";
    private Date seniority = new Date();
    private Boolean isLoggedIn = false;
    private int counter = 0;

    //</editor-fold>
    //<editor-fold desc="Constructor">

    public TravelUsScreenScraper(String name, String badge, Date seniority) {
        setName(name);
        setBadge(badge);
        setSeniority(seniority);
    }

    //</editor-fold>
    //<editor-fold desc="Properties">

    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

            }

            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[]{};
            }
        }};

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection
                    .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Date getSeniority() {
        return seniority;
    }

    void setSeniority(Date seniority) {
        this.seniority = seniority;
    }

    public String getBadge() {
        return badge;
    }

    void setBadge(String badge) {
        this.badge = badge;
    }

    public String getName() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }

    public Boolean getIsLoggedIn() {
        return isLoggedIn;
    }

    private String getRandom() {
        return String.valueOf(Math.random());
    }

    private int getOffset() {
        return TimeZone.getDefault().getRawOffset();
    }

    //</editor-fold>
    //<editor-fold desc="Public Methods">

    public Boolean doLogin(String username, String password)
            throws TravelUsException {

        log(STARTING);

        Connection.Response res;
        Boolean result;
        Document doc;
        String csrf;

        try {
            mUsername = username;
            mPassword = password;

            trustAllHosts();
            try {
                res = Jsoup.connect(URI_ITINERARY)
                        .userAgent(USER_AGENT)
                        .timeout(mTimeout)
                        .ignoreHttpErrors(true)
                        .followRedirects(true)
                        .execute();
            } catch (UnknownHostException e) {
                throw new TravelUsException("Travel US is unresponsive, please try again later");
            }

            doc = res.parse();
            csrf = doc.select("input[name=csrf]").val();
            sessionId = res.cookie(JSESSIONID);
            nsc = res.cookie(NSC);

            res = Jsoup.connect(URI_SECURITY_CHECK)
                    .timeout(mTimeout)
                    .userAgent(USER_AGENT)
                    .header("Accept", "text/html, application/xhtml+xml, */*")
                    .header("Accept-Language", "en-US,en;q=0.7,ru;q=0.3")
                    .header("Content-Type", "application/x-www-form-urlencoded")
                    .header("Accept-Encoding", "gzip, deflate")
                    .header("Host", "travel.usairways.com")
                    .header("DNT", ONE)
                    .header("Connection", "Keep-Alive")
                    .header("Cache-Control", "no-cache")
                    .data("j_username", username)
                    .data("j_password", password)
                    .data("csrf", csrf)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS1, EMPTY)
                    .referrer(URI_ITINERARY)
                    .method(Connection.Method.POST).execute();
            doc = res.parse();
            sessionId = res.cookie(JSESSIONID);
            profileSession = doc.select(IFRAME).first().id().substring(14, 36);

            result = !doc.text().contains("You have entered an incorrect user name or password");

            isLoggedIn = result;
            log(String.format("end... returning %s", result));
            if (!result) log(doc.text());
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        return result;
    }

    public List<Reservation> getProfile(int daysToSearch)
            throws TravelUsException {

        log(STARTING);

        List<Reservation> reservations = new ArrayList<>();
        String bookingType;
        String isActive;
        Element table;
        Document doc;
        String pnr;
        Date date;

        try {
            doc = getProfilePage();

            ParseUserData(doc);

            table = doc.select(PROFILE_TABLE).get(1);
            profileSession = doc.select(IFRAME).first().id().substring(14, 36);
            for (Element row : table.select(DATA_TABLE_ROW)) {
                try {
                    pnr = row.select(PROFILE_PNR).text();
                    date = DATE.parse(row.select(PROFILE_TRAVEL_DATE).text());
                    bookingType = row.select(PROFILE_BOOKING_TYPE).text();
                    isActive = row.select(PROFILE_IS_PNR_ACTIVE).html();


                    if (Math.abs((date.getTime() - new Date().getTime()) / DAY) <= daysToSearch
                            && isActive.length() == 0)
                        reservations.add(new Reservation(pnr, date, bookingType));
                } catch (Exception e) {
                    log(e.getMessage());
                }
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(String.format("end... returning %d reservations(s)", reservations.size()));

        return reservations;
    }

    public Boolean doCheckin(String badge, String pnr, String origin)
            throws TravelUsException {

        log(String.format("starting... (origin = %s)", origin));

        Boolean returnValue;
        try {
            Connection conn;
            String session;
            Element table;
            Document doc;

            doc = Jsoup.connect(URI_CHECK_IN).referrer(URI_ITINERARY)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, "")
                    .timeout(mTimeout).get();

            if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
                if (!doLogin(mUsername, mPassword))
                    throw new TravelUsException("Unable to login");
                else {
                    doc = Jsoup.connect(URI_CHECK_IN).referrer(URI_ITINERARY)
                            .cookie(JSESSIONID, sessionId).cookie(ICE_SESSIONS, "")
                            .timeout(mTimeout).get();
                }
            }

            session = doc.select(IFRAME).first().id().substring(14, 36);

            conn = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_CHECK_IN)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, session + "#1")
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .data("ice.event.target", "checkinForm:btnLookup")
                    .data("ice.event.captured", "checkinForm:btnLookup")
                    .data("checkinForm:btnLookup", "Submit")
                    .data("checkinForm", "")
                    .data("javax.faces.RenderKitId", "")
                    .data("checkinForm:selCode", badge.trim().length() > 6
                            ? badge.substring(0, 2)
                            : "US")
                    .data("checkinForm:badgeNum", badge)
                    .data("checkinForm:confirmCd", pnr)
                    .data("checkinForm:board", origin)
                    .data("checkinForm:board_idx", "")
                    .data("checkinForm:passportFirstName", "")
                    .data("checkinForm:passportMiddleName", "")
                    .data("checkinForm:passportLastName", "")
                    .data("checkinForm:passportNumber", "")
                    .data("checkinForm:passportExpirationDate", "")
                    .data("checkinForm:passportExpirationMonth", "")
                    .data("checkinForm:passportExpirationYear", "")
                    .data("checkinForm:issuingcountry", "Select a country")
                    .data("checkinForm:nationality", "Select a country")
                    .data("checkinForm:residentcountry", "Select a country")
                    .data("checkinForm:j_id252", "")
                    .data("checkinForm:j_id255", "")
                    .data("checkinForm:j_id256", "")
                    .data("checkinForm:j_id257", "")
                    .data("checkinForm:j_id258", "")
                    .data("checkinForm:j_id259", "false")
                    .data("checkinForm:j_id260", "false")
                    .data("checkinForm:addressLine1", "")
                    .data("checkinForm:city", "")
                    .data("checkinForm:state", "")
                    .data("checkinForm:postalCode", "")
                    .data("ice.session", session)
                    .data("ice.view", "1")
                    .data("ice.focus", "checkinForm:btnLookup")
                    .data("rand", getRandom())
                    .timeout(mTimeout);
            addStandardInput(conn);
            doc = conn.post();

            doc = extractInnerHTML(doc, 2);

            if (doc == null) {
                log("Unable to extract inner HTML");
                return false;
            }

            table = doc.select(CHECKIN_TABLE).get(0);

            int pax = table.select(SPAN).size();


            conn = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(String.format(URI_CHECKIN_CONFIRMATION, origin, pnr, badge))
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, session + "#1")
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .data("ice.event.target", "checkinForm:btnConfirm")
                    .data("ice.event.captured", "checkinForm:btnConfirm")
                    .data("checkinForm:btnConfirm", "Check In")
                    .data("checkinForm", "")
                    .data("javax.faces.RenderKitId", "")
                    .data("checkinForm:selCode", "US")
                    .data("checkinForm:badgeNum", badge)
                    .data("checkinForm:confirmCd", pnr)
                    .data("checkinForm:board", origin)
                    .data("checkinForm:board_idx", "")
                    .data("checkinForm:hazmat", "on")
                    .data("checkinForm:passportFirstName", "")
                    .data("checkinForm:passportMiddleName", "")
                    .data("checkinForm:passportLastName", "")
                    .data("checkinForm:passportNumber", "")
                    .data("checkinForm:passportExpirationDate", "")
                    .data("checkinForm:passportExpirationMonth", "")
                    .data("checkinForm:passportExpirationYear", "")
                    .data("checkinForm:issuingcountry", "Select a country")
                    .data("checkinForm:nationality", "Select a country")
                    .data("checkinForm:residentcountry", "Select a country")
                    .data("checkinForm:j_id252", "")
                    .data("checkinForm:j_id255", "")
                    .data("checkinForm:j_id256", "")
                    .data("checkinForm:j_id257", "")
                    .data("checkinForm:j_id258", "")
                    .data("checkinForm:j_id259", "false")
                    .data("checkinForm:j_id260", "false")
                    .data("checkinForm:addressLine1", "")
                    .data("checkinForm:city", "")
                    .data("checkinForm:state", "")
                    .data("checkinForm:postalCode", "");

            for (int i = 0; i < pax; i++)
                conn.data(String.format("checkinForm:j_id59:%d:j_id63", i), "on");
            addStandardInput(conn);

            doc = conn.data("ice.session", session)
                    .data("ice.view", "1")
                    .data("ice.focus", "checkinForm:btnLookup")
                    .data("rand", getRandom()).timeout(mTimeout).post();

            returnValue = doc.html().contains(CHECKIN_SUCCESS);
            if (!returnValue)
                log(doc.html());

            log(String.format("end... returning %s", returnValue.toString()));
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        return returnValue;
    }

    public Tuple<List<Flight>, List<Passenger>> getReservationDetails(String pnr)
            throws TravelUsException {

        log(STARTING);

        List<Flight> flights;
        List<Passenger> pax;
        Connection con;
        Element table;
        Document doc;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_PROFILE)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .data(TARGET, LIST_PNR)
                    .data(CAPTURED, LIST_PNR)
                    .data(PROFILE_FORM, PROFILE_FORM)
                    .data(JAVAX_FACES, IC_EFACES_RENDER_KIT)
                    .data(PROFILE_FORM_PHONE_NUM, EMPTY)
                    .data(PROFILE_FORM_EMAIL_ADDRESS, EMPTY)
                    .data(PROFILE_FORM_ELIGIBLE_FIRST_NAME_HIDDEN, EMPTY)
                    .data(PROFILE_FORM_ELIGIBLE_LAST_NAME_HIDDEN, EMPTY)
                    .data(PROFILE_FORM_ELIGIBLE_ID_HIDDEN, EMPTY)
                    .data(PROFILE_FORM_ELIGIBLE_PASSWORD, EMPTY)
                    .data(PROFILE_FORM_ELIGIBLE_PASSWORD_CONFIRM, EMPTY)
                    .data(PROFILE_FORM_TO, EMPTY)
                    .data(PROFILE_FORM_LOGIN_ACTION_HIDDEN, EMPTY)
                    .data(PROFILE_FORM_J_ID135_EXPANDED, EMPTY)
                    .data(PROFILE_FORM_REC_LOOKUP, EMPTY)
                    .data(PROFILE_FORM_J_ID183_EXPANDED, EMPTY)
                    .data(PROFILE_FORM_J_ID208_EXPANDED, EMPTY)
                    .data(PROFILE_FORM_IDCL, LIST_PNR)
                    .data(RECLOC, pnr)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, LIST_PNR)
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .method(Connection.Method.POST);
            addStandardInput(con);
            con.execute();

            doc = Jsoup.connect(URI_PNR_DETAILS).referrer(URI_PROFILE)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .timeout(mTimeout).get();

            table = doc.getElementById(PROFILE_DETAILS_ITINERARY);
            flights = getFlights(table, pnr);

            table = doc.getElementById(PROFILE_DETAILS_PASSENGERS);
            pax = getPassengers(table);

            Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .timeout(mTimeout).get();
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(String.format("end... returning %d flight(s) and %d passenger(s)", flights.size(), pax.size()));

        return new Tuple<>(flights, pax);
    }

    public Flight checkAvailability(Flight flight)
            throws TravelUsException {

        log(String.format("starting... (Flight = %s)", flight));

        String session;
        Document doc;

        try {
            doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, EMPTY)
                    .timeout(mTimeout).get();
            session = doc.select(IFRAME).first().id().substring(14, 36);

            if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
                if (!doLogin(mUsername, mPassword))
                    throw new TravelUsException("Unable to login");
                else {

                    doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                            .cookie(JSESSIONID, sessionId)
                            .cookie(NSC, nsc)
                            .cookie(ICE_SESSIONS, EMPTY)
                            .timeout(mTimeout).get();
                    session = doc.select(IFRAME).first().id().substring(14, 36);
                }
            }

            Connection con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_CHECK_PBT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, session + ADD_ONE)
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .data(TARGET, CHECK_PBT_FORM_SEARCH)
                    .data(CAPTURED, CHECK_PBT_FORM_SEARCH)
                    .data(CHECK_PBT_FORM_SEARCH, GET_PBT_DETAIL)
                    .data(CHECK_PBT_FORM, EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data(CHECK_PBT_FORM_FLIGHT_NUMBER, String.valueOf(flight.getFlightNumber()))
                    .data(CHECK_PBT_FORM_BOARD, flight.getOrigin())
                    .data(CHECK_PBT_FORM_BOARD_IDX, EMPTY)
                    .data(CHECK_PBT_FORM_DEST, flight.getDestination())
                    .data(CHECK_PBT_FORM_DEST_IDX, EMPTY)
                    .data(CHECK_PBT_FORM_DEPTDATE, SHORT_DATE.format(flight.getDeparture()))
                    .data(CHECK_PBT_FORM_IDCL, CHECK_PBT_FORM_J_ID69_0_J_ID103)
                    .data(CHECK_PBT_FORM_IDCHECK_PBT_FORM_DEPTDATESP, EMPTY)
                    .data(ICE_SESSION, session)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, CHECK_PBT_FORM_J_ID69_0_J_ID103)
                    .data(RAND, getRandom())
                    .timeout(mTimeout);
            addStandardInput(con);

            doc = con.post();

            flight = parseAvailability(flight, doc);
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);

        return flight;
    }

    public ArrayList<Flight> searchFlights(FlightSearchParams params)
            throws TravelUsException {

        log(STARTING);
        log("   Origin = " + params.getOrigin() + " to " + params.getDestination());

        ArrayList<Flight> flights = new ArrayList<>();
        Date startFunction = new Date();

        Document doc;
        Connection con;

        try {
            Jsoup.connect("https://travel.usairways.com/us/logout.htm")
                    .referrer(URI_ITINERARY)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .timeout(mTimeout).get();
            doLogin(mUsername, mPassword);
            Document login = Jsoup.connect(URI_ITINERARY)
                    .referrer(URI_PROFILE)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .userAgent(USER_AGENT)
                    .timeout(mTimeout).get();

            profileSession = login.select(IFRAME).first().id().substring(14, 36);

            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .cookie(NSC, nsc)
                    .data(TARGET, ITINERARY_FORM_SEARCH)
                    .data(CAPTURED, ITINERARY_FORM_SEARCH)
                    .data(ITINERARY_FORM_SEARCH, SEARCH)
                    .data(ITINERARY_FORM, EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data(ITINERARY_FORM_BOOKING_TYPE, SA)
                    .data(ITINERARY_FORM_TRIP_TYPE, params.isOneWay() ? ONE_WAY : ROUND_TRIP)
                    .data(ITINERARY_FORM_BOARD, params.getOrigin())
                    .data(ITINERARY_FORM_BOARD_IDX, EMPTY)
                    .data(ITINERARY_FORM_DEST, params.getDestination())
                    .data(ITINERARY_FORM_DEST_IDX, EMPTY)
                    .data(ITINERARY_FORM_DD, DATE.format(params.getDeparture()))
                    .data(ITINERARY_FORM_J_ID50, ANY);
            addStandardInput(con);

            if (!params.isOneWay())
                con.data(ITINERARY_FORM_RD, DATE.format(params.getReturn()))
                        .data(ITINERARY_FORM_RTOD, ANY);
            else
                con.data(ITINERARY_FORM_IDCL, EMPTY);

            doc = con
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, ITINERARY_FORM_SEARCH)
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (doc.select(UPDATE).first().select(BOOK_FLIGHT_SEARCH_ERROR).size() == 1 ||
                    doc.select("attribute").text().contains("errorLabel"))
                throw new TravelUsException(doc.select(UPDATE).first().select(CONTENT).text());

            doc = extractInnerHTML(doc, 0);

            extractFlights(params.getDeparture(), flights, doc);

            log(String.format("Function returns %d flight(s) in %s seconds",
                    flights.size(),
                    String.valueOf((new Date().getTime() - startFunction.getTime()) / SECOND)));
            for(Flight flight : flights)
                log("Flight " + flight.toString()
                        + " SA = " + (flight.getCoach() != null ? flight.getCoach().getSeatsAvailable() : "NA"));
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }
        return flights;
    }

    public ArrayList<Flight> searchReturnFlights(FlightSearchParams params)
            throws TravelUsException {

        log(STARTING);

        ArrayList<Flight> flights = new ArrayList<>();
        Date startFunction = new Date();
        Document doc;
        Connection con;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(ICE_SUBMIT_PARTIAL, TRUE)
                    .data(TARGET, "itineraryForm:searchResult:1:j_id420")
                    .data(CAPTURED, "itineraryForm:searchResult:1.1")
                    .data(ICE_EVENT_TYPE, ONCLICK)
                    .data(ICE_EVENT_ALT, FALSE)
                    .data(ICE_EVENT_CTRL, FALSE)
                    .data(ICE_EVENT_SHIFT, FALSE)
                    .data(ICE_EVENT_META, UNDEFINED)
                    .data(ICE_EVENT_X, X)
                    .data(ICE_EVENT_Y, Y)
                    .data(ICE_EVENT_LEFT, FALSE)
                    .data(ICE_EVENT_RIGHT, FALSE)
                    .data(ITINERARY_FORM, EMPTY)
                    .data(ICEFACES_CSS_UPDATES, EMPTY)
                    .data(JAVAX_FACES_VIEW_STATE, ONE)
                    .data(JAVAX_FACES, EMPTY)
                    .data(ITINERARY_FORM_BOOKING_TYPE, SA)
                    .data(ITINERARY_FORM_TRIP_TYPE, ROUND_TRIP)
                    .data(ITINERARY_FORM_BOARD, params.getOrigin())
                    .data(ITINERARY_FORM_BOARD_IDX, EMPTY)
                    .data(ITINERARY_FORM_DEST, params.getDestination())
                    .data(ITINERARY_FORM_DEST_IDX, EMPTY)
                    .data(ITINERARY_FORM_DD, DATE.format(params.getDeparture()))
                    .data(ITINERARY_FORM_J_ID50, ANY);

            if (!params.isOneWay())
                con.data(ITINERARY_FORM_RD, DATE.format(params.getReturn()))
                        .data(ITINERARY_FORM_RTOD, ANY);

            doc = con
                    .data(FIRST_PANE, HEIGHT_100_WIDTH_77)
                    .data(SECOND_PANE, HEIGHT_100_WIDTH_20)
                    .data(PERCENT, EMPTY)
                    .data(SEARCH_RESULT, EMPTY)
                    .data(ITINERARY_FORM_IDCL, "itineraryForm:searchResult:1")
                    .data("itineraryForm:searchResult:1.1", "itineraryForm:searchResult:1.1")
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, ITINERARY_FORM_SEARCH)
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (doc.select(UPDATE).first().select(BOOK_FLIGHT_SEARCH_ERROR).size() == 1 ||
                    doc.select("attribute").text().contains("errorLabel"))
                throw new TravelUsException(doc.select(UPDATE).first().select(CONTENT).text());

            doc = extractInnerHTML(doc, 3);

            extractFlights(params.getDeparture(), flights, doc);

            log(String.format("Function returns %d flight(s) in %s seconds",
                    flights.size(),
                    String.valueOf((new Date().getTime() - startFunction.getTime()) / SECOND)));
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }
        return flights;
    }

    public void selectFlight(Flight flight)
            throws TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .cookie(NSC, nsc)
                    .data(TARGET, flight.getFlightIndex())
                    .data(CAPTURED, flight.getFlightIndex())
                    .data(ITINERARY_FORM, EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data(ITINERARY_FORM_BOOKING_TYPE, SA)
                    .data(ITINERARY_FORM_TRIP_TYPE, ONE_WAY)
                    .data(ITINERARY_FORM_BOARD, flight.getOrigin())
                    .data(ITINERARY_FORM_BOARD_IDX, EMPTY)
                    .data(ITINERARY_FORM_DEST, flight.getDestination())
                    .data(ITINERARY_FORM_DEST_IDX, EMPTY)
                    .data(ITINERARY_FORM_DD, DATE.format(flight.getDeparture()))
                    .data(ITINERARY_FORM_J_ID50, ANY);

            addStandardInput(con);

            doc = con
                    .data(FIRST_PANE, HEIGHT_100_WIDTH_77)
                    .data(SECOND_PANE, HEIGHT_100_WIDTH_20)
                    .data(PERCENT, EMPTY)
                    .data(SEARCH_RESULT, EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(flight.getFlightIndex(), EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, flight.getFlightIndex())
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (!doc.text().contains(String.valueOf(flight.getFlightNumber()))) {
                log(doc.html());
                throw new TravelUsException("Unable to add flight to Itinerary");
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);
    }

    public void unSelectFlight(Flight flight)
            throws TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;


        try {
            String s = flight.getFlightIndex();
            Integer i = Integer.valueOf(s.substring(s.length() - 1));
            s = s.substring(0, s.length() - 1) + String.valueOf(i + 1);

            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(TARGET, s)
                    .data(CAPTURED, s)
                    .data(ITINERARY_FORM, EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data(ITINERARY_FORM_BOOKING_TYPE, SA)
                    .data(ITINERARY_FORM_TRIP_TYPE, ONE_WAY)
                    .data(ITINERARY_FORM_BOARD, flight.getOrigin())
                    .data(ITINERARY_FORM_BOARD_IDX, EMPTY)
                    .data(ITINERARY_FORM_DEST, flight.getDestination())
                    .data(ITINERARY_FORM_DEST_IDX, EMPTY)
                    .data(ITINERARY_FORM_DD, DATE.format(flight.getDeparture()))
                    .data(ITINERARY_FORM_J_ID50, ANY);

            addStandardInput(con);

            doc = con
                    .data(FIRST_PANE, HEIGHT_100_WIDTH_77)
                    .data(SECOND_PANE, HEIGHT_100_WIDTH_20)
                    .data(PERCENT, EMPTY)
                    .data(SEARCH_RESULT, EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(s, EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, s)
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            doc = extractInnerHTML(doc, 3);

            if (!doc.data().equals(EMPTY)) {
                log(doc.html());
                throw new TravelUsException("Unable to remove flight from Itinerary");
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);
    }

    public List<Passenger> getAvailablePassengers(String origin, String destination, String departure)
            throws TravelUsException {

        log(STARTING);

        List<Passenger> passengers = new ArrayList<>();
        Document doc;
        Connection con;
        String fullName;
        String login;
        String id;
        String phone;
        String email;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .cookie(NSC, nsc)
                    .data(TARGET, "itineraryForm:bookItineraryPanel-accept")
                    .data(CAPTURED, "itineraryForm:bookItinerary")
                    .data("itineraryForm:bookItinerary", "Continue")
                    .data(ITINERARY_FORM, EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data(ITINERARY_FORM_BOOKING_TYPE, SA)
                    .data(ITINERARY_FORM_TRIP_TYPE, ONE_WAY)
                    .data(ITINERARY_FORM_BOARD, origin)
                    .data(ITINERARY_FORM_BOARD_IDX, EMPTY)
                    .data(ITINERARY_FORM_DEST, destination)
                    .data(ITINERARY_FORM_DEST_IDX, EMPTY)
                    .data(ITINERARY_FORM_DD, departure)
                    .data(ITINERARY_FORM_J_ID50, ANY);
            addStandardInput(con);

            doc = con
                    .data(FIRST_PANE, HEIGHT_100_WIDTH_77)
                    .data(SECOND_PANE, HEIGHT_100_WIDTH_20)
                    .data(PERCENT, EMPTY)
                    .data(SEARCH_RESULT, EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, "itineraryForm:bookItinerary")
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (!doc.html().contains("redirect url=\"/us/search/passengers.jsf\"")) {
                log(doc.html());
                throw new TravelUsException("Unable to verify all flights were selected");
            }

            doc = Jsoup.connect(URI_SELECT_PASSENGERS)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .timeout(mTimeout)
                    .get();

            phone = doc.select("td[id=passengersForm:j_id59-0-1]").first().select("input").attr("value");
            email = doc.select("td[id=passengersForm:j_id64-0-1]").first().select("input").attr("value");

            for (Element element : doc.select("table[class=iceSelMnyCb]").first().select(TR)) {
                fullName = element.text();
                id = element.select("input").first().attr("id");
                login = element.select("input").first().attr("value");
                passengers.add(new Passenger(fullName, id, login, phone, email));
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(String.format("Parsed %d passengers", passengers.size()));

        log(END);
        return passengers;
    }

    public Passenger selectPassenger(Passenger pax, StringList selectedPax)
            throws TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_SELECT_PASSENGERS)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(TARGET, pax.getId())
                    .data(CAPTURED, "passengersForm:eligibles")
                    .data("passengersForm", EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data("passengersForm:phoneNum", pax.getPhone())
                    .data("passengersForm:phoneNumType", "M")
                    .data("passengersForm:emailAddress", pax.getEmail())
                    .data("passengersForm:guestPassNum", "0")
                    .data("passengersForm:currency", "USZ")
                    .data("passengersForm:divMilesId", EMPTY);

            for (String s : selectedPax.getStrings())
                con.data("passengersForm:eligibles", s);

            addStandardInput(con);

            doc = con
                    .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                    .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                    .data("passengersForm:j_id32InPercent", EMPTY)
                    .data("passengersForm:etDateOfBirth", EMPTY)
                    .data("passengersForm:etGender", "FEMALE")
                    .data("passengersForm:etRedressNumber", EMPTY)
                    .data("passengersForm:etKnownTraveler", EMPTY)
                    .data("passengersForm:gpFirstName", EMPTY)
                    .data("passengersForm:gpMiddleName", EMPTY)
                    .data("passengersForm:gpLastName", EMPTY)
                    .data("passengersForm:j_id225", "NONE")
                    .data("passengersForm:gpDateOfBirth", EMPTY)
                    .data("passengersForm:gpGender", "FEMALE")
                    .data("passengersForm:gpRedressNumber", EMPTY)
                    .data("passengersForm:gpKnownTraveler", EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, EMPTY)
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            Document d1 = extractInnerHTML(doc, 0);
            if (!d1.text().contains(pax.getName()))
                d1 = extractInnerHTML(doc, 1);
            if (!d1.text().contains(pax.getName()))
                d1 = extractInnerHTML(doc, 2);
            if (!d1.text().contains(pax.getName()))
                d1 = extractInnerHTML(doc, 3);
            if (!d1.text().contains(pax.getName())) {
                log(doc.html());
                throw new TravelUsException("Unable to select passenger");
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);

        return pax;
    }

    public float getPrice(Passenger pax, List<String> selectedPax)
            throws TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;
        float price;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_SELECT_PASSENGERS)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(TARGET, "passengersForm:j_id78")
                    .data(CAPTURED, "passengersForm:j_id78")
                    .data("passengersForm:j_id78", "Price It!")
                    .data("passengersForm", EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data("passengersForm:phoneNum", pax.getPhone())
                    .data("passengersForm:phoneNumType", "M")
                    .data("passengersForm:emailAddress", pax.getEmail())
                    .data("passengersForm:guestPassNum", "0")
                    .data("passengersForm:currency", "USZ")
                    .data("passengersForm:divMilesId", EMPTY);

            for (String s : selectedPax)
                con.data("passengersForm:eligibles", s);

            addStandardInput(con);

            doc = con
                    .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                    .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                    .data("passengersForm:j_id32InPercent", EMPTY)
                    .data("passengersForm:etDateOfBirth", EMPTY)
                    .data("passengersForm:etGender", "FEMALE")
                    .data("passengersForm:etRedressNumber", EMPTY)
                    .data("passengersForm:etKnownTraveler", EMPTY)
                    .data("passengersForm:gpFirstName", EMPTY)
                    .data("passengersForm:gpMiddleName", EMPTY)
                    .data("passengersForm:gpLastName", EMPTY)
                    .data("passengersForm:j_id229", "NONE")
                    .data("passengersForm:gpDateOfBirth", EMPTY)
                    .data("passengersForm:gpGender", "FEMALE")
                    .data("passengersForm:gpRedressNumber", EMPTY)
                    .data("passengersForm:gpKnownTraveler", EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, "passengersForm:j_id78")
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            Document d = extractInnerHTML(doc, 0);

            if (!d.text().contains("Total:"))
                throw new TravelUsException("Unable to price PNR");

            price = Float.parseFloat(d.select("span[id=passengersForm:j_id269]").first().text().substring(1));

            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_SELECT_PASSENGERS)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(TARGET, "passengersForm:j_id245")
                    .data(CAPTURED, "passengersForm:j_id245")
                    .data("passengersForm:j_id245", "X")
                    .data("passengersForm", EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data("passengersForm:phoneNum", pax.getPhone())
                    .data("passengersForm:phoneNumType", "M")
                    .data("passengersForm:emailAddress", pax.getEmail())
                    .data("passengersForm:guestPassNum", "0")
                    .data("passengersForm:currency", "USZ")
                    .data("passengersForm:divMilesId", EMPTY);

            for (String s : selectedPax)
                con.data("passengersForm:eligibles", s);

            addStandardInput(con);

            con
                    .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                    .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                    .data("passengersForm:j_id32InPercent", EMPTY)
                    .data("passengersForm:etDateOfBirth", EMPTY)
                    .data("passengersForm:etGender", "FEMALE")
                    .data("passengersForm:etRedressNumber", EMPTY)
                    .data("passengersForm:etKnownTraveler", EMPTY)
                    .data("passengersForm:gpFirstName", EMPTY)
                    .data("passengersForm:gpMiddleName", EMPTY)
                    .data("passengersForm:gpLastName", EMPTY)
                    .data("passengersForm:j_id225", "NONE")
                    .data("passengersForm:gpDateOfBirth", EMPTY)
                    .data("passengersForm:gpGender", "FEMALE")
                    .data("passengersForm:gpRedressNumber", EMPTY)
                    .data("passengersForm:gpKnownTraveler", EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, "passengersForm:j_id245")
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);

        return price;
    }

    public Reservation completeBooking(Passenger pax, StringList selectedPax)
            throws TravelUsException {

        Reservation res = new Reservation(EMPTY, new Date(), EMPTY);
        log(STARTING);

        Document doc;
        Connection con;

        try {
            // send 'continue' request
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_SELECT_PASSENGERS)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(TARGET, "passengersForm:j_id169")
                    .data(CAPTURED, "passengersForm:j_id169")
                    .data("passengersForm:j_id169", "Continue")
                    .data("passengersForm", EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data("passengersForm:phoneNum", pax.getPhone())
                    .data("passengersForm:phoneNumType", "M")
                    .data("passengersForm:emailAddress", pax.getEmail())
                    .data("passengersForm:guestPassNum", "0")
                    .data("passengersForm:currency", "USZ")
                    .data("passengersForm:divMilesId", EMPTY);

            for (String s : selectedPax.getStrings())
                con.data("passengersForm:eligibles", s);

            addStandardInput(con);

            con
                    .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                    .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                    .data("passengersForm:j_id32InPercent", EMPTY)
                    .data("passengersForm:etDateOfBirth", EMPTY)
                    .data("passengersForm:etGender", "FEMALE")
                    .data("passengersForm:etRedressNumber", EMPTY)
                    .data("passengersForm:etKnownTraveler", EMPTY)
                    .data("passengersForm:gpFirstName", EMPTY)
                    .data("passengersForm:gpMiddleName", EMPTY)
                    .data("passengersForm:gpLastName", EMPTY)
                    .data("passengersForm:j_id225", "NONE")
                    .data("passengersForm:gpDateOfBirth", EMPTY)
                    .data("passengersForm:gpGender", "FEMALE")
                    .data("passengersForm:gpRedressNumber", EMPTY)
                    .data("passengersForm:gpKnownTraveler", EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, "passengersForm:j_id169")
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            // agree to the pop up stating you understand hazardous materials clause
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_SELECT_PASSENGERS)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .data(TARGET, "passengersForm:j_id308")
                    .data(CAPTURED, "passengersForm:j_id308")
                    .data("passengersForm:j_id308", "Continue")
                    .data("passengersForm", EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data("passengersForm:phoneNum", pax.getPhone())
                    .data("passengersForm:phoneNumType", "M")
                    .data("passengersForm:emailAddress", pax.getEmail())
                    .data("passengersForm:guestPassNum", "0")
                    .data("passengersForm:currency", "USZ")
                    .data("passengersForm:divMilesId", EMPTY);

            for (String s : selectedPax.getStrings())
                con.data("passengersForm:eligibles", s);

            addStandardInput(con);

            doc = con
                    .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                    .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                    .data("passengersForm:j_id32InPercent", EMPTY)
                    .data("passengersForm:etDateOfBirth", EMPTY)
                    .data("passengersForm:etGender", "FEMALE")
                    .data("passengersForm:etRedressNumber", EMPTY)
                    .data("passengersForm:etKnownTraveler", EMPTY)
                    .data("passengersForm:gpFirstName", EMPTY)
                    .data("passengersForm:gpMiddleName", EMPTY)
                    .data("passengersForm:gpLastName", EMPTY)
                    .data("passengersForm:j_id225", "NONE")
                    .data("passengersForm:gpDateOfBirth", EMPTY)
                    .data("passengersForm:gpGender", "FEMALE")
                    .data("passengersForm:gpRedressNumber", EMPTY)
                    .data("passengersForm:gpKnownTraveler", EMPTY)
                    .data("passengersForm:hazmatAccepted", "on")
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, "passengersForm:j_id308")
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (!doc.html().contains("us/search/confirmation.jsf")) {
                log(doc.html());
                throw new TravelUsException("Unable to complete booking");
            }

            doc = Jsoup.connect(URI_BOOKING_CONFIRMATION)
                    .referrer(URI_SELECT_PASSENGERS)
                    .userAgent(USER_AGENT)
                    .cookie(NSC, nsc)
                    .cookie(JSESSIONID, sessionId).get();

            String pnr = doc.getElementById("confirmationForm:j_id32").text().substring(32);
            res.setPnr(pnr);
            for (Element element : doc.select("tr[id^=confirmationForm:j_id76]")) {
                try {
                    Flight flight = new Flight();
                    String flightNumber = element.select("span[id$=0:j_id80]").text();
                    String departure = element.select("span[id$=0:j_id84]").text();
                    String origin = element.select("span[id$=0:j_id88]").text();
                    String destination = element.select("span[id$=0:j_id92]").text();
                    String departureTime = element.select("span[id$=0:j_id96]").text();
                    String arrival = element.select("span[id$=0:j_id101]").text();

                    Date arrivalTime = new Date();
                    Integer flightNum = Integer.parseInt(flightNumber);
                    Date departureDate = LONG_DATE.parse(departure);
                    Date departureTimeParsed = SHORT_TIME.parse(departureTime);
                    departureDate = new Date(departureDate.getTime() + departureTimeParsed.getTime() + getOffset());
                    if (arrival.length() > 0) {
                        arrivalTime = SHORT_TIME.parse(arrival);
                        arrivalTime = new Date(departureDate.getTime() + arrivalTime.getTime() + getOffset());
                    }
                    flight.setFlightNumber(flightNum);
                    flight.setOrigin(origin);
                    flight.setDestination(destination);
                    flight.setDeparture(departureDate);
                    flight.setArrival(arrivalTime);

                    res.getFlights().add(flight);


                    if (element.select("span[id$=1:j_id80]").text().length() > 0) {
                        flight = new Flight();
                        flightNumber = element.select("span[id$=1:j_id80]").text();
                        departure = element.select("span[id$=1:j_id84]").text();
                        origin = element.select("span[id$=1:j_id88]").text();
                        destination = element.select("span[id$=1:j_id92]").text();
                        departureTime = element.select("span[id$=1:j_id96]").text();
                        arrival = element.select("span[id$=1:j_id101]").text();

                        arrivalTime = new Date();
                        flightNum = Integer.parseInt(flightNumber);
                        departureDate = LONG_DATE.parse(departure);
                        departureTimeParsed = SHORT_TIME.parse(departureTime);
                        departureDate = new Date(departureDate.getTime() + departureTimeParsed.getTime() + getOffset());
                        if (arrival.length() > 0) {
                            arrivalTime = SHORT_TIME.parse(arrival);
                            arrivalTime = new Date(departureDate.getTime() + arrivalTime.getTime() + getOffset());
                        }
                        flight.setFlightNumber(flightNum);
                        flight.setOrigin(origin);
                        flight.setDestination(destination);
                        flight.setDeparture(departureDate);
                        flight.setArrival(arrivalTime);

                        res.getFlights().add(flight);
                    }

                } catch (NumberFormatException e) {
                    log("==exception==");
                    log(e.getMessage());
                    log("=============");
                    log(element.text());
                    log("=============");
                    log(element.html());
                    log("=====end=====");
                }
            }

            for (Element element : doc.select("td[id=confirmationForm:j_id115-2-0]").select("tbody").select(TR)) {
                try {
                    String name = element.select("span[id$=:j_id123]").text();
                    String relationship = element.select("span[id$=:j_id126]").text();
                    String ticketNumber = element.select("span[id$=:j_id129]").text();

                    res.getPassengers().add(new Passenger(name, relationship, ticketNumber));
                } catch (Exception e) {
                    log("==exception==");
                    log(e.getMessage());
                    log("=============");
                    log(element.text());
                    log("=============");
                    log(element.html());
                    log("=====end=====");
                }
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);
        return res;
    }

    public List<String> getNotices()
            throws TravelUsException {

        log(STARTING);

        Document doc;
        List<String> notices = new ArrayList<>();

        try {
            doc = getProfilePage();
            for (Element row : doc.getElementById("profileForm:alerts").select("tbody").select("span[id$=:j_id126]"))
                notices.add(row.text());

            log(String.format("Returning %d notices", notices.size()));
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }
        log(END);

        return notices;
    }

    public void togglePBT(Flight flight) throws TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;

        try {
            String formEntry = flight.isTracked()
                    ? flight.getFlightIndex().substring(0, 54) + ":j_id162:0:j_id164"
                    : flight.getFlightIndex().substring(0, 54) + ":j_id162:0:j_id165";
            log(formEntry);
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .cookie(NSC, nsc)
                    .data(TARGET, formEntry)
                    .data(CAPTURED, formEntry)
                    .data(ITINERARY_FORM, EMPTY)
                    .data(JAVAX_FACES, EMPTY)
                    .data(ITINERARY_FORM_BOOKING_TYPE, SA)
                    .data(ITINERARY_FORM_TRIP_TYPE, ONE_WAY)
                    .data(ITINERARY_FORM_BOARD, flight.getOrigin())
                    .data(ITINERARY_FORM_BOARD_IDX, EMPTY)
                    .data(ITINERARY_FORM_DEST, flight.getDestination())
                    .data(ITINERARY_FORM_DEST_IDX, EMPTY)
                    .data(ITINERARY_FORM_DD, SHORT_DATE.format(flight.getDeparture()))
                    .data(ITINERARY_FORM_J_ID50, ANY);
            addStandardInput(con);
            doc = con
                    .data(FIRST_PANE, HEIGHT_100_WIDTH_77)
                    .data(SECOND_PANE, HEIGHT_100_WIDTH_20)
                    .data(PERCENT, EMPTY)
                    .data(SEARCH_RESULT, EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(formEntry, EMPTY)
                    .data(ITINERARY_FORM_IDCL, EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, formEntry)
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (!doc.text().contains(flight.getFlightIndex().substring(0, 54))) {
                log(doc.text());
                throw new TravelUsException("Unable to add flight to PBT Tracker");
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        log(END);
    }

    public List<Flight> pbtTracker()
            throws TravelUsException {

        log(STARTING);

        List<Flight> flights = new ArrayList<>();
        Integer coachSpaceAvailable;
        Integer firstSpaceAvailable;
        String departurePoint;
        Integer firstCapacity;
        Integer coachCapacity;
        Integer flightNumber;
        Integer firstNonRev;
        Integer coachNonRev;
        Date departureTime;
        String destination;
        Date departureDate;
        String flightId;
        String origin;
        Integer start;
        Document doc;
        Integer end;

        try {
            doc = Jsoup.connect(URI_PBT_TRACKER).referrer(URI_ITINERARY)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(NSC, nsc)
                    .cookie(ICE_SESSIONS, EMPTY)
                    .timeout(mTimeout).get();

            if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
                if (!doLogin(mUsername, mPassword))
                    throw new TravelUsException("Unable to login");
                else {

                    doc = Jsoup.connect(URI_PBT_TRACKER).referrer(URI_ITINERARY)
                            .cookie(JSESSIONID, sessionId)
                            .cookie(NSC, nsc)
                            .cookie(ICE_SESSIONS, EMPTY)
                            .timeout(mTimeout).get();
                }
            }

            for (Element row : doc.select(DATA_TABLE_ROW)) {
                // let's just catch all exceptions
                // because input is not sanitized and
                // may contain flights in the past with only a few fields filled out
                try {
                    flightNumber = Integer.valueOf(row.select(PBT_FLIGHT_NUMBER).text().substring(3));
                    departurePoint = row.select(PBT_DEPARTURE_POINT).text();
                    origin = departurePoint.substring(0, 3);
                    start = departurePoint.indexOf(PARENTHESIS_START);
                    end = departurePoint.lastIndexOf(PARENTHESIS_END);
                    departureTime = SHORT_TIME.parse(departurePoint.substring(start + 1, end));
                    destination = row.select(PBT_DESTINATION).text();
                    departureDate = DATE.parse(row.select(PBT_DEPARTURE_DATE).text());
                    firstCapacity = Integer.valueOf(row.select(PBT_FIRST_CAPACITY).text());
                    firstSpaceAvailable = Integer.valueOf(row.select(PBT_FIRST_SEATS_AVAILABLE).text());
                    firstNonRev = Integer.valueOf(row.select(PBT_NON_REVS).text());
                    coachCapacity = Integer.valueOf(row.select(PBT_COACH_CAPACITY).text());
                    coachSpaceAvailable = Integer.valueOf(row.select(PBT_COACH_SEATS_AVAILABLE).text());
                    coachNonRev = Integer.valueOf(row.select(PBT_COACH_NON_REVS).text());
                    flightId = row.select(PBT_REMOVE).attr("name");
                    departureDate = new Date(departureDate.getTime() + departureTime.getTime() + getOffset());
                    Flight flight = new Flight(flightNumber, origin, destination, departureDate, firstCapacity,
                            firstSpaceAvailable, firstNonRev, coachCapacity, coachSpaceAvailable, coachNonRev);
                    flight.setFlightIndex(flightId);
                    flights.add(flight);

                } catch (Exception e) {
                    log(e.getMessage());
                }
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception e) {
            throw new TravelUsException(e);
        }

        return flights;
    }

    public void removePBTTracking(Flight flight) throws TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;

        try {
            con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                    .referrer(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .cookie(JSESSIONID, sessionId)
                    .cookie(ICE_SESSIONS, profileSession + ADD_ONE)
                    .cookie(NSC, nsc)
                    .data(TARGET, flight.getFlightIndex())
                    .data(CAPTURED, flight.getFlightIndex())
                    .data("pbtTrackerForm", "pbtTrackerForm");
            addStandardInput(con);
            doc = con
                    .data("javax.faces.RenderKitId", "ICEfacesRenderKit")
                    .data("pbtTrackerForm:_idcl", EMPTY)
                    .data(flight.getFlightIndex(), EMPTY)
                    .data(ICE_SESSION, profileSession)
                    .data(ICE_VIEW, ONE)
                    .data(ICE_FOCUS, flight.getFlightIndex())
                    .data(RAND, getRandom())
                    .timeout(mTimeout)
                    .post();

            if (!doc.text().contains("Flight #")) {
                log(doc.text());
                throw new TravelUsException("Unable to add flight to PBT Tracker");
            }
        } catch (HttpStatusException e) {
            throw new TravelUsUnavailableException(e);
        } catch (Exception ex) {
            log(ex.getMessage());
            throw new TravelUsException(ex);
        }
    }
    //</editor-fold>
    //<editor-fold desc="Private Methods">

    private Document getProfilePage() throws IOException, TravelUsException {

        Document doc;
        doc = Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .timeout(mTimeout).get();

        if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {
                doc = Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                        .cookie(JSESSIONID, sessionId)
                        .cookie(NSC, nsc)
                        .userAgent(USER_AGENT)
                        .timeout(mTimeout).get();
            }
        }
        return doc;
    }

    private void extractFlights(Date desiredDeparture, List<Flight> flights, Document doc) {

        Integer flightNumber;
        String departureOrigin;
        Date departureDate;
        Integer start;
        Integer end;
        String actualOrigin;
        String arrivalString;
        Date arrivalDate;
        String destinationReturned;
        String firstClassString;
        Integer coach;
        String flightIndex;
        Boolean isTracked;

        for (Element row : doc.select(DATA_TABLE_ROW)) {
            try {
                flightNumber = Integer.parseInt(row.select(BOOK_FLIGHT_NUMBER_0).text().substring(3));
                departureOrigin = row.select(BOOK_DEPARTURE_0).text();
                departureDate = new Date(desiredDeparture.getTime()
                        + SHORT_TIME.parse(departureOrigin).getTime() + getOffset());
                start = departureOrigin.indexOf(PARENTHESIS_START);
                end = departureOrigin.lastIndexOf(PARENTHESIS_END);
                actualOrigin = departureOrigin.substring(start + 1, end);

                arrivalString = row.select(BOOK_ARRIVAL_0).text();
                if (arrivalString.equals(EMPTY))
                    arrivalString = row.select(BOOK_ARRIVAL_ALT_0).text();
                arrivalDate = new Date(desiredDeparture.getTime()
                        + SHORT_TIME.parse(arrivalString).getTime() + getOffset());
                if (arrivalDate.getTime() < departureDate.getTime())
                    arrivalDate = new Date(arrivalDate.getTime() + DAY);
                start = arrivalString.indexOf(PARENTHESIS_START);
                end = arrivalString.lastIndexOf(PARENTHESIS_END);
                destinationReturned = arrivalString.substring(start + 1, end);

                firstClassString = row.select(BOOK_FIRST_CLASS_0).text();
                coach = Integer.parseInt(row.select(BOOK_COACH_0).text());

                flightIndex = row.select("td[id$=0:j_id196-0-0]").select("input").first().attr("name");
                isTracked = row.html().contains("darkPurpleT.gif");

                flights.add(new Flight(flightNumber, departureDate, actualOrigin,
                        destinationReturned, arrivalDate,
                        firstClassString.length() == 0 ? 0 : Integer.parseInt(firstClassString),
                        coach, flightIndex, isTracked));

                if (row.select(BOOK_FLIGHT_NUMBER_1).text().length() > 0)
                    extractSecondLeg(desiredDeparture, flights, row);

                if (row.select(BOOK_FLIGHT_NUMBER_2).text().length() > 0)
                    extractThirdLeg(desiredDeparture, flights, row);
            } catch (Exception e) {
                log(e.getMessage());
            }
        }
    }

    private void extractSecondLeg(Date desiredDeparture, List<Flight> flights, Element row)
            throws ParseException {

        Integer flightNumber;
        String departureOrigin;
        Date departureDate;
        Integer start;
        Integer end;
        String actualOrigin;
        String arrivalString;
        Date arrivalDate;
        String destinationReturned;
        String firstClassString;
        Integer coach;
        String flightIndex;
        Boolean isTracked;

        flightNumber = Integer.parseInt(row.select(BOOK_FLIGHT_NUMBER_1).text().substring(3));
        departureOrigin = row.select(BOOK_DEPARTURE_1).text();
        departureDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(departureOrigin).getTime() + getOffset());
        start = departureOrigin.indexOf(PARENTHESIS_START);
        end = departureOrigin.lastIndexOf(PARENTHESIS_END);
        actualOrigin = departureOrigin.substring(start + 1, end);

        arrivalString = row.select(BOOK_ARRIVAL_1).text();
        if (arrivalString.equals(EMPTY))
            arrivalString = row.select(BOOK_ARRIVAL_ALT_1).text();
        arrivalDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(arrivalString).getTime() + getOffset());
        if (arrivalDate.getTime() < departureDate.getTime())
            arrivalDate = new Date(arrivalDate.getTime() + DAY);
        start = arrivalString.indexOf(PARENTHESIS_START);
        end = arrivalString.lastIndexOf(PARENTHESIS_END);
        destinationReturned = arrivalString.substring(start + 1, end);

        firstClassString = row.select(BOOK_FIRST_CLASS_1).text();
        coach = Integer.parseInt(row.select(BOOK_COACH_1).text());

        flightIndex = row.select("td[id$=1:j_id196-0-0]").select("input").first().attr("name");
        isTracked = row.text().contains("darkPurpleT.gif");

        flights.add(new Flight(flightNumber, departureDate, actualOrigin,
                destinationReturned, arrivalDate,
                firstClassString.length() == 0 ? 0 : Integer.parseInt(firstClassString),
                coach, flightIndex, isTracked));
    }

    private void extractThirdLeg(Date desiredDeparture, List<Flight> flights, Element row)
            throws ParseException {

        Integer flightNumber;
        String departureOrigin;
        Date departureDate;
        Integer start;
        Integer end;
        String actualOrigin;
        String arrivalString;
        Date arrivalDate;
        String destinationReturned;
        String firstClassString;
        Integer coach;
        String flightIndex;
        Boolean isTracked;

        flightNumber = Integer.parseInt(row.select(BOOK_FLIGHT_NUMBER_2).text().substring(3));
        departureOrigin = row.select(BOOK_DEPARTURE_2).text();
        departureDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(departureOrigin).getTime() + getOffset());
        start = departureOrigin.indexOf(PARENTHESIS_START);
        end = departureOrigin.lastIndexOf(PARENTHESIS_END);
        actualOrigin = departureOrigin.substring(start + 1, end);

        arrivalString = row.select(BOOK_ARRIVAL_2).text();
        if (arrivalString.equals(EMPTY))
            arrivalString = row.select(BOOK_ARRIVAL_ALT_2).text();
        arrivalDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(arrivalString).getTime() + getOffset());
        if (arrivalDate.getTime() < departureDate.getTime())
            arrivalDate = new Date(arrivalDate.getTime() + DAY);
        start = arrivalString.indexOf(PARENTHESIS_START);
        end = arrivalString.lastIndexOf(PARENTHESIS_END);
        destinationReturned = arrivalString.substring(start + 1, end);

        firstClassString = row.select(BOOK_FIRST_CLASS_2).text();
        coach = Integer.parseInt(row.select(BOOK_COACH_2).text());

        flightIndex = row.select("td[id$=2:j_id196-0-0]").select("input").first().attr("name");
        isTracked = row.text().contains("darkPurpleT.gif");

        flights.add(new Flight(flightNumber, departureDate, actualOrigin,
                destinationReturned, arrivalDate,
                firstClassString.length() == 0 ? 0 : Integer.parseInt(firstClassString),
                coach, flightIndex, isTracked));
    }

    private void addStandardInput(Connection con) {
        con.data(ICE_SUBMIT_PARTIAL, FALSE)
                .data(ICE_EVENT_TYPE, ONCLICK)
                .data(ICE_EVENT_ALT, FALSE)
                .data(ICE_EVENT_CTRL, FALSE)
                .data(ICE_EVENT_SHIFT, FALSE)
                .data(ICE_EVENT_META, UNDEFINED)
                .data(ICE_EVENT_X, X)
                .data(ICE_EVENT_Y, Y)
                .data(ICE_EVENT_LEFT, FALSE)
                .data(ICE_EVENT_RIGHT, FALSE)
                .data(ICEFACES_CSS_UPDATES, EMPTY)
                .data(JAVAX_FACES_VIEW_STATE, ONE);
    }

    private List<Passenger> getPassengers(Element table) {

        log(STARTING);

        Element row;
        String name;
        String relationship;
        String ticket;

        List<Passenger> passengers = new ArrayList<>();
        for (Element element : table.select(DATA_TABLE_ROW)) {
            row = element;
            name = row.select(SPAN).get(0).text();
            relationship = row.select(SPAN).get(1).text();
            ticket = row.select(SPAN).get(2).text();

            passengers.add(new Passenger(name, relationship, ticket));
        }

        log(String.format("end... returning %d passengers", passengers.size()));

        return passengers;
    }

    private List<Flight> getFlights(Element table, String pnr)
            throws ParseException {

        log(STARTING);

        List<Flight> flights = new ArrayList<>();
        Element row;
        int flightNumber;
        Date departure;
        String origin;
        String destination;
        Date departureTime;
        Date arrival;

        for (Element element : table.select(DATA_TABLE_ROW)) {
            row = element;
            flightNumber = Integer.parseInt(row.select(SPAN).get(0).text());
            departure = LONG_DATE.parse(row.select(SPAN).get(1).text());
            origin = row.select(SPAN).get(2).text();
            destination = row.select(SPAN).get(3).text();
            departureTime = SHORT_TIME.parse(row.select(SPAN).get(4).text());
            arrival = SHORT_TIME.parse(row.select(SPAN).get(5).text());
            arrival = new Date(departure.getTime() + arrival.getTime() + getOffset());
            departure = new Date(departure.getTime() + departureTime.getTime() + getOffset());

            flights.add(new Flight(flightNumber, departure, origin,
                    destination, arrival, false, pnr, false));
        }

        log(String.format("end... returning %d flights", flights.size()));

        return flights;
    }

    private Flight parseAvailability(Flight flight, Document doc) {

        log(STARTING);

        Element table;

        try {
            Document d = extractInnerHTML(doc, 0);
            if (d == null) {
                log("Unable to extract inner HTML");
                return flight;
            }

            if (d.text().length() > 10) {

                counter = 0;
                table = d.getElementById(CHECK_PBT_FORM_AVAILABILITY1);
                for (Element row : table.select(CHECK_PBT_DATA_TABLE_ROW)) {

                    if (row.select(SPAN).get(1).text().contains(CHECK_PBT_FIRST)) continue;

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_TOTAL_CAPACITY)) {
                        flight.getFirstClass().setCapacity(Integer.valueOf(row.select(SPAN).get(1).text().equals(EMPTY) ? "0" : row.select(SPAN).get(1).text()));
                        flight.getCoach().setCapacity(Integer.valueOf(row.select(SPAN).get(2).text().equals(EMPTY) ? "0" : row.select(SPAN).get(2).text()));
                        continue;
                    }

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_AUTHORIZED)) continue;

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_BOOKED)) continue;

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_SPACE_AVAILABLE)) {
                        flight.getFirstClass().setSeatsAvailable(Integer.valueOf(row.select(SPAN).get(1).text().equals(EMPTY) ? "0" : row.select(SPAN).get(1).text()));
                        flight.getCoach().setSeatsAvailable(Integer.valueOf(row.select(SPAN).get(2).text().equals(EMPTY) ? "0" : row.select(SPAN).get(2).text()));
                        continue;
                    }

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_NON_REVS_LISTED)) {
                        flight.getFirstClass().setNonRevsCount(Integer.valueOf(row.select(SPAN).get(1).text().equals(EMPTY) ? "0" : row.select(SPAN).get(1).text()));
                        flight.getCoach().setNonRevsCount(Integer.valueOf(row.select(SPAN).get(2).text().equals(EMPTY) ? "0" : row.select(SPAN).get(2).text()));
                    }
                }
            } else if (counter > 3) {
                return flight;
            } else {
                counter = counter + 1;
                return checkAvailability(flight);
            }

        } catch (Exception e) {
            log("===============");
            log(e.getMessage());
            log("===============");
        }

        log(String.format("end... returning flight = %s", flight.toString()));

        return flight;
    }

    private Document extractInnerHTML(Document doc, int index) {

        int size = doc.select(CONTENT).size();
        if (size <= index) return null;

        return Jsoup.parse(StringEscapeUtils.unescapeXml(doc.select(CONTENT).get(index).text()));
    }

    private void ParseUserData(Document doc)
            throws ParseException {
        String longName = doc.getElementById(PROFILE_NAME).text().toUpperCase();
        longName = longName.substring(longName.lastIndexOf(' ') + 1);

        if (longName.length() < 5) longName += "\\";
        else longName = longName.substring(0, 5);

        setName(longName);
        setBadge(doc.getElementById(PROFILE_BADGE).text());
        setSeniority(DATE.parse(doc.getElementById(PROFILE_SENIORITY).text()));
    }

    //</editor-fold>
}
